﻿using System.Collections.Generic;
using System.Threading.Tasks.Dataflow;
using NLog;
#if CTPSE || ROHON_CTP
using QuantBox.Sfit.Api;
#elif CTPMINI
using QuantBox.SfitMini.Api;
#endif

namespace QuantBox.XApi
{
    internal class CtpDealProcessor
    {
        #region Order Event Class
        private static class OrderEventType
        {
            public const byte NewOrder = 0;
            public const byte OrderCancel = 1;
            public const byte OrderRsp = 2;
        }
        private abstract class OrderEvent
        {
            public abstract byte TypeId { get; }
        }
        private class NewOrderEvent : OrderEvent
        {
            public readonly OrderField Order;
            public NewOrderEvent(OrderField order)
            {
                Order = order;
            }
            public override byte TypeId => OrderEventType.NewOrder;
        }
        private class CancelOrderEvent : OrderEvent
        {
            public readonly string Id;

            public CancelOrderEvent(string id)
            {
                Id = id;
            }

            public override byte TypeId => OrderEventType.OrderCancel;
        }
        private class OrderRspEvent : OrderEvent
        {
            public readonly CtpResponse Response;

            public OrderRspEvent(CtpResponse response)
            {
                Response = response;
            }

            public override byte TypeId => OrderEventType.OrderRsp;
        }
        #endregion

        private readonly CtpTradeClient _client;
        private readonly ActionBlock<OrderEvent> _action;
        private readonly OrderMap _orders;
        private readonly HashSet<string> _tradeSet = new HashSet<string>();
        private readonly HashSet<string> _cancelPending = new HashSet<string>();
        private readonly List<CtpTrade> _tradePending = new List<CtpTrade>();
        private readonly Logger _logger;

        private void ReportOrder(OrderField order, ExecType execType, OrderStatus newStatus, CtpRspInfo rspInfo)
        {
            ReportOrder(order, execType, newStatus, rspInfo.ErrorID, rspInfo.ErrorID, rspInfo.ErrorMsg);
        }

        private void ReportOrder(
            OrderField order,
            ExecType execType,
            OrderStatus newStatus,
            int errorId = 0, int rawErrorId = 0, string text = "")
        {
            order.Status = newStatus;
            var report = order.Clone();
            report.ExecType = execType;
            if (!string.IsNullOrEmpty(text))
            {
                report.XErrorID = errorId;
                report.RawErrorID = rawErrorId;
                report.SetText(text);
            }
            _client.spi.ProcessRtnOrder(report);
        }

        private void OrderAction(OrderEvent e)
        {
            switch (e.TypeId)
            {
                case OrderEventType.NewOrder:
                    ProcessNewOrder((NewOrderEvent)e);
                    return;
                case OrderEventType.OrderCancel:
                    ProcessCancelOrder((CancelOrderEvent)e);
                    return;
            }
            var rsp = ((OrderRspEvent)e).Response;
            switch (rsp.TypeId)
            {
                case CtpResponseType.OnErrRtnOrderAction:
                    ProcessCancelReject(rsp.Item1.AsOrderAction, rsp.Item2);
                    break;
                case CtpResponseType.OnErrRtnOrderInsert:
                    ProcessOrderReject(rsp.Item1.AsInputOrder, rsp.Item2);
                    break;
                case CtpResponseType.OnRspOrderAction:
                    ProcessCancelReject(rsp.Item1.AsInputOrderAction, rsp.Item2);
                    break;
                case CtpResponseType.OnRspOrderInsert:
                    ProcessOrderReject(rsp.Item1.AsInputOrder, rsp.Item2);
                    break;
                case CtpResponseType.OnRtnOrder:
                    ProcessRtnOrder(rsp.Item1.AsOrder);
                    break;
                case CtpResponseType.OnRtnTrade:
                    ProcessRtnTrade(rsp.Item1.AsTrade);
                    break;
            }
        }

        private void ProcessTradePending()
        {
            if (_tradePending.Count == 0)
            {
                return;
            }
            var list = new List<CtpTrade>(_tradePending);
            _tradePending.Clear();
            foreach (var trade in list)
            {
                ProcessRtnTrade(trade, false);
            }
        }

        private void ProcessRtnTrade(CtpTrade data, bool sendLog = true)
        {
            if (sendLog)
            {
                _client.spi.ProcessLog(new LogField(LogLevel.Trace, "RtnTrade"));
            }
            if (_orders.TryGetBySysId(data.OrderSysID, out var order))
            {
                if (_tradeSet.Contains(data.TradeID))
                {
                    _logger.Warn($"重复的成交,oid:{data.OrderSysID},tid:{data.TradeID}");
                    return;
                }
                _tradeSet.Add(data.TradeID);

                _logger.Debug($"新成交,id:{order.ID},oid:{data.OrderSysID},tid:{data.TradeID},text:{order.Text()}");
                var trade = CtpConvert.GetTrade(data);
                trade.ID = order.ID;
                _client.spi.ProcessRtnTrade(trade);
            }
            else
            {
                _logger.Debug($"成交暂存,oid:{data.OrderSysID},tid:{data.TradeID}");
                _tradePending.Add(data);
            }
        }

        private void ProcessRtnOrder(CtpOrder data)
        {
            var status = CtpConvert.GetOrderStatus(data);

            if (_client.requestBase > 0 && data.RequestID > 0 && data.RequestID / _client.requestBase != 1)
            {
                _logger.Debug($"外部会话订单,oid:{data.OrderSysID},reqid:{data.RequestID},{status}");
                return;
            }

            if (!_orders.TryGetByOrderRef(data.OrderRef, out var order) || order == null)
            {
                _logger.Debug($"外部系统订单,oid:{data.OrderSysID},{status}");
                return;
            }

            if (order.OrderIsDone())
            {
                _logger.Debug($"更新已完成订单,oid:{order.OrderID},new:{status},old:{order.Status}");
                return;
            }

            _logger.Debug($"订单回报,id:{order!.ID},ref:{data.OrderRef},oid:{data.OrderSysID},{status},text:{order.Text()}");
            if (data.OrderSysID == order.OrderID && status == order.Status)
            {
                _logger.Debug($"订单状态重复,id:{order!.ID},ref:{data.OrderRef},oid:{data.OrderSysID},{status}");
                return;
            }

            if (status == OrderStatus.Cancelled && order.ExecType != ExecType.PendingCancel)
            {
                _logger.Warn($"外部撤销,id:{order!.ID},ref:{data.OrderRef},oid:{data.OrderSysID},text:{order.Text()}");
            }

            var id = order.ID;
            if (!string.IsNullOrEmpty(data.OrderSysID) && string.IsNullOrEmpty(order.OrderID))
            {
                order.OrderID = data.OrderSysID;
                order.ExchangeID = data.ExchangeID;
                _orders.SetMap(order);
                ProcessTradePending();
            }
            ReportOrder(
                order,
                CtpConvert.GetExecType(data),
                CtpConvert.GetOrderStatus(data),
                0,
                0,
                data.StatusMsg);
            if (_cancelPending.Contains(id))
            {
                _cancelPending.Remove(id);
                ProcessCancelOrder(id);
            }
        }

        #region Cancel Reject
        private void ProcessCancelReject(string orderSysId, CtpRspInfo rspInfo)
        {
            if (rspInfo.ErrorID != 0)
            {
                return;
            }
            if (_orders.TryGetBySysId(orderSysId, out var order))
            {
                _logger.Debug($"撤单失败,id:{order.ID},oid:{order.OrderID},{rspInfo.ErrorMsg}");
            }
        }
        private void ProcessCancelReject(CtpOrderAction action, CtpRspInfo rspInfo)
        {
            ProcessCancelReject(action.OrderSysID, rspInfo);
        }
        private void ProcessCancelReject(CtpInputOrderAction action, CtpRspInfo rspInfo)
        {
            ProcessCancelReject(action.OrderSysID, rspInfo);
        }
        #endregion

        #region Order Reject
        private void ProcessOrderReject(string orderRef, CtpRspInfo rspInfo)
        {
            if (_orders.TryGetByOrderRef(orderRef, out var order))
            {
                _logger.Debug($"无效订单,id:{order!.ID},ref:{orderRef},{rspInfo.ErrorMsg},text:{order.Text()}");
                ReportOrder(order, ExecType.Rejected, OrderStatus.Rejected, rspInfo);
            }
            else
            {
                _logger.Debug($"外部无效订单,ref:{orderRef},{rspInfo.ErrorMsg}");
            }
        }
        private void ProcessOrderReject(CtpInputOrder input, CtpRspInfo rspInfo)
        {
            ProcessOrderReject(input.OrderRef, rspInfo);
        }
        #endregion

        private void ProcessCancelOrder(string id)
        {
            if (_cancelPending.Contains(id))
            {
                _logger.Warn($"重复撤单,id:{id}");
                return;
            }

            if (!_orders.TryGetById(id, out var order))
            {
                _logger.Warn($"撤销已完成的订单,id:{id}");
                return;
            }

            if (string.IsNullOrEmpty(order.OrderID))
            {
                _logger.Debug($"撤单缓存,id:{id}");
                _cancelPending.Add(id);
                return;
            }

            _logger.Debug($"订单撤销,id:{id},oid:{order.OrderID},text:{order.Text()}");
            var action = new CtpInputOrderAction
            {
                OrderSysID = order.OrderID,
                ExchangeID = order.ExchangeID.ToUpper(),
                InstrumentID = order.InstrumentID,
                ActionFlag = CtpActionFlagType.Delete,
                OrderActionRef = _client.GetNextRequestId(),
                InvestorID = _client.ctpLoginInfo!.UserID,
                BrokerID = _client.ctpLoginInfo.BrokerID
            };
            order.ExecType = ExecType.PendingCancel;
            _client.api!.ReqOrderAction(action, _client.GetNextRequestId());
        }

        private void ProcessCancelOrder(CancelOrderEvent e)
        {
            ProcessCancelOrder(e.Id);
        }

        private void ProcessNewOrder(NewOrderEvent e)
        {
            _orders.AddOrder(e.Order);
            if (e.Order.Status == OrderStatus.NotSent)
            {
                var data = CtpConvert.GetInputOrder(e.Order);
                _logger.Debug($"订单发送,id:{e.Order.ID},ref:{data.OrderRef},text:{e.Order.Text()}");
                data.InvestorID = _client.ctpLoginInfo!.UserID;
                data.BrokerID = _client.ctpLoginInfo.BrokerID;
                e.Order.ExecType = ExecType.PendingNew;
                var ret = _client.api!.ReqOrderInsert(data, _client.GetNextRequestId());
                if (ret == 0)
                {
                    return;
                }

                e.Order.ExecType = ExecType.Rejected;
                ProcessOrderReject(data, new CtpRspInfo
                {
                    ErrorID = -1,
                    ErrorMsg = "连接中断发送失败"
                });
            }
        }

        public CtpDealProcessor(CtpTradeClient client)
        {
            _client = client;
            _logger = client.logger;
            _action = new ActionBlock<OrderEvent>(OrderAction);
            _orders = new OrderMap();
        }

        public void Post(OrderField order)
        {
            _action.Post(new NewOrderEvent(order));
        }

        public void Post(string id)
        {
            _action.Post(new CancelOrderEvent(id));
        }

        public void Post(ref CtpResponse rsp)
        {
            _action.Post(new OrderRspEvent(rsp));
        }

        public void Close()
        {
            _action.Complete();
            _action.Completion.Wait();
        }
    }
}
